package com.framework.doc;


import com.framework.doc.bean.CustomBeanDoc;
import com.framework.doc.bean.CustomFieldDoc;
import com.framework.doc.bean.CustomInterfaceDoc;
import com.framework.doc.bean.CustomModuleDoc;
import com.framework.utils.GsonTools;
import com.sun.javadoc.*;
import org.springframework.beans.BeanUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


/**
 * 生成JavaDoc工具类,自定义doclet.
 * 此类以main方法的com.sun.tools.javadoc.Main.execute()启动.
 * 以-doclet CustomDoclet指定javadoc以此类生成文档文件(默认为HtmlDoclet).
 * 需包含public static boolean start(RootDoc root)函数为入口,
 * 其中RootDoc包含了整个扫描的java文件注释,所有对注释的操作基于此类.
 */
public class CustomDoclet {

    /**
     * 接口doc数据
     */
    private static final List<CustomModuleDoc> customModuleDocs = new ArrayList<>();

    /**
     * 类doc数据
     */
    private static final List<CustomBeanDoc> customBeanDocs = new ArrayList<>();

    public static boolean start(RootDoc root) {
        ClassDoc[] classes = root.classes();
        Arrays.asList(classes).forEach(item -> {

            //首先判断java文件是否是bean或接口
            //处理模块
            if (isModule(item)) {
                CustomModuleDoc customModuleDoc = getCustomModuleDoc(item);

                //定义接口组
                List<CustomInterfaceDoc> customInterfaceDocs = new ArrayList<>();

                //对于模块,封装当前模块中接口信息
                Arrays.asList(item.methods()).forEach(methodDoc -> {
                    CustomInterfaceDoc customInterfaceDoc = getCustomInterfaceDoc(methodDoc);
                    customInterfaceDocs.add(customInterfaceDoc);
                });

                customModuleDoc.setCustomInterfaceDocs(customInterfaceDocs);
                customModuleDocs.add(customModuleDoc);
            }
            //处理Bean
            if (isBean(item)) {
                CustomBeanDoc customBeanDoc = getCustomBeanDoc(item);

                //定义字段组
                List<CustomFieldDoc> customFieldDocs = new ArrayList<>();

                //对于bean,封装当前Bean中的字段信息
                Arrays.asList(item.fields()).forEach(fieldDoc -> {
                    CustomFieldDoc customFieldDoc = getCustomFieldDoc(fieldDoc,1,null);
                    customFieldDocs.add(customFieldDoc);
                });

                customBeanDoc.setCustomFieldDocs(customFieldDocs);
                customBeanDocs.add(customBeanDoc);
            }
            //处理到此处,项目中包含@API与@Bean的类都已缓存起来,等待进一步处理
        });

        processInterface();

        String s = GsonTools.toGsonString(customModuleDocs);
        System.out.println(s);
        System.out.println();
        System.out.println();
        System.out.println();
        System.out.println();
        System.out.println();
        return true;
    }


    public static void main(String[] args) {
        List<String> docArgs = new ArrayList<>();
        docArgs.add("-doclet");
        docArgs.add(CustomDoclet.class.getName());

        docArgs.add("-private");
        //扫描获取项目java文件名数组
        List<String> filePaths = FileUtils.getAllFilesName(System.getProperty("user.dir") + String.format("%ssrc%smain%sjava",File.separator,File.separator,File.separator));
        docArgs.addAll(filePaths);

        //执行自定义doclet,此处需要处理注释的java文件需要将文件名以数组形式传入
        com.sun.tools.javadoc.Main.execute((String[]) docArgs.toArray(new String[docArgs.size()]));
    }

    /**
     * 根据ClassDoc封装模块注释信息
     * @param classDoc ClassDoc
     * @return CustomModuleDoc 封装的模块注释信息
     */
    private static CustomModuleDoc getCustomModuleDoc(ClassDoc classDoc) {
        //获取类上面对应的标签信息
        String description = getClassTag(classDoc, "description");
        String moduleName = getClassTag(classDoc, "moduleName");
        String uri = getClassTag(classDoc, "uri");
        String version = getClassTag(classDoc, "version");
        String author = getClassTag(classDoc, "author");

        //组装Interface对象
        CustomModuleDoc customModuleDoc= new CustomModuleDoc();
        customModuleDoc.setAuthor(author);
        customModuleDoc.setDescription(description);
        customModuleDoc.setModuleName(moduleName);
        customModuleDoc.setVersion(version);
        customModuleDoc.setUri(uri);

        return customModuleDoc;
    }

    /**
     * 根据ClassDoc获取封装Bean注释信息
     * @param classDoc ClassDoc
     * @return CustomBeanDoc 封装Bean注释信息
     */
    private static CustomBeanDoc getCustomBeanDoc(ClassDoc classDoc) {
        //获取类上面对应的标签信息
        String beanName = classDoc.name();

        //组装CustomBeanDoc对象
        CustomBeanDoc customBeanDoc= new CustomBeanDoc();
        customBeanDoc.setName(beanName);

        return customBeanDoc;
    }


    private static String getClassTag(ClassDoc classDoc, String tag){
        Tag[] tags = classDoc.tags(tag);
        if (tags !=null && tags.length != 0){
            return tags[0].text();
        }
        return null;
    }

    /**
     * 根据MethodDoc获取接口封装注释信息
     * @param methodDoc doc类的方法
     * @return CustomInterfaceDoc 依据方法注释封装的接口注释对象
     */
    private static CustomInterfaceDoc getCustomInterfaceDoc(MethodDoc methodDoc) {
        //接口描述
        String description = getMethodTag(methodDoc, "description");
        //请求Bean
        String requestBean = getMethodTag(methodDoc, "requestBean");
        //请求示例
        String requestExample = getMethodTag(methodDoc, "requestExample");

        //请求类型
        String requestType = getMethodTag(methodDoc, "requestType");

        //响应Bean  其实此处可以从方法返回值类型取出Bean类型,以及获取泛型内容,此处为了注释统一,要求注释包含responseBean
        //获取泛型内的类型
        //String typeName = methodDoc.returnType().asParameterizedType().typeArguments()[0].typeName();
        String responseBean = getMethodTag(methodDoc, "responseBean");

        //响应示例
        String responseExample = getMethodTag(methodDoc, "responseExample");
        //接口名称
        String name = methodDoc.name();

        //封装接口注释信息
        CustomInterfaceDoc customInterfaceDoc = new CustomInterfaceDoc();
        customInterfaceDoc.setDescription(description);
        customInterfaceDoc.setName(name);
        customInterfaceDoc.setRequestBeanName(requestBean);
        customInterfaceDoc.setRequestExample(requestExample);
        customInterfaceDoc.setResponseBeanName(responseBean);
        customInterfaceDoc.setResponseExample(responseExample);
        customInterfaceDoc.setRequestType(requestType);

        return customInterfaceDoc;
    }
    private static String getMethodTag(MethodDoc methodDoc, String tag){
        Tag[] tags = methodDoc.tags(tag);
        if (null != tags && tags.length != 0) {
            return tags[0].text();
        }
        return null;
    }

    /**
     * 根据FieldDoc获取属性封装信息
     * @param fieldDoc 属性的注释
     * @return CustomFieldDoc 依据属性注释封装的对象
     */
    private static CustomFieldDoc getCustomFieldDoc(FieldDoc fieldDoc,int num,String parentName) {

        String fieldType = fieldDoc.type().typeName();

        String description = getFieldTag(fieldDoc, "description");
        String isRequire = getFieldTag(fieldDoc,"isRequire");
        String name = fieldDoc.name();

        //对于首次扫描的Field,默认为一级,无子节点
        CustomFieldDoc customFieldDoc = new CustomFieldDoc();
        customFieldDoc.setDescription(description);
        customFieldDoc.setName(name);
        customFieldDoc.setType(fieldType);
        customFieldDoc.setHasChild(false);
        customFieldDoc.setLevel(num);
        customFieldDoc.setIsRequire(isRequire);
        if (num > 1) {
            customFieldDoc.setParentName(parentName);
        } else {
            customFieldDoc.setParentName("data");
        }

        //如果字段不是基本类型/或者是数组泛型
        ClassDoc classDoc = fieldDoc.type().asClassDoc();
        if ("List".equals(fieldType)) {
            classDoc = fieldDoc.type().asParameterizedType().typeArguments()[0].asClassDoc();
        }
        if (num < 4 && (null != classDoc && isBean(classDoc))) {
            CustomBeanDoc customBeanDoc = getCustomBeanDoc(classDoc);
            //定义字段组
            List<CustomFieldDoc> customFieldDocs = new ArrayList<>();
            Arrays.asList(classDoc.fields()).forEach(item -> {
                CustomFieldDoc field = getCustomFieldDoc(item, num + 1,customFieldDoc.getName());
                customFieldDocs.add(field);
            });
            customBeanDoc.setCustomFieldDocs(customFieldDocs);

            customFieldDoc.setHasChild(true);
            customFieldDoc.setChild(customBeanDoc);
        }

        return customFieldDoc;
    }

    /**
     * 根据标签获取字段的注释内容
     * @param fieldDoc 字段注释节点
     * @param tag   注释符号
     * @return 返回注释文本内容
     */
    private static String getFieldTag(FieldDoc fieldDoc, String tag){
        Tag[] tags = fieldDoc.tags(tag);
        if (null != tags && tags.length != 0) {
            return tags[0].text();
        }
        return null;
    }

    /**
     * 对接口注释封装,处理为前端使用的格式
     */
    private static void processInterface() {

        //遍历模块
        customModuleDocs.forEach(item->{
            //遍历模块中的接口
            item.getCustomInterfaceDocs().forEach(customInterfaceDoc -> {
                //对于每一个接口,根据requestBeanName与responseBeanName填充requestBean与responseBean
                customBeanDocs.forEach(customBeanDoc -> {
                    if(customBeanDoc.getName().equals(customInterfaceDoc.getRequestBeanName())) {
                        customInterfaceDoc.setRequestBean(customBeanDoc);
                    } else if (customBeanDoc.getName().equals(customInterfaceDoc.getResponseBeanName())
                            || customInterfaceDoc.getResponseBeanName().contains(customBeanDoc.getName())) {
                        customInterfaceDoc.setResponseBean(customBeanDoc);
                    }
                });
            });
        });
    }

    /**
     * doclet支持泛型
     * @return 版本号
     */
    public static LanguageVersion languageVersion() {
        return LanguageVersion.JAVA_1_5;
    }

    /**
     * 判断是否是模块
     * @param classDoc ClassDoc
     * @return boolean
     */
    private static boolean isModule(ClassDoc classDoc){
        Tag[] tags = classDoc.tags("API");
        return null != tags && tags.length != 0;
    }

    /**
     * 判断是否是Bean
     * @param classDoc ClassDoc
     * @return boolean
     */
    private static boolean isBean(ClassDoc classDoc) {
        Tag[] tags = classDoc.tags("Bean");
        return null != tags && tags.length != 0;
    }
}
